#include "DHT11_Config.h"

/* 全局变量定义 ---------------------------------------------------------------*/
u8 DHT11_Buf[5] = {0};  // 存储DHT11数据的缓冲区，格式：[湿度整数, 湿度小数, 温度整数, 温度小数, 校验和]

/**
    ******************************************************************************
    * 函数名称：DHT11_Output_Config
    * 功能描述：配置PG11为推挽输出模式（用于主机控制总线）
    * 输入参数：无
    * 返回值  ：无
    * 详细说明：
    *   1. 初始化时需将总线拉高
    *   2. 切换为输出模式用于发送起始信号
    * 硬件要求：
    *   - 必须连接4.7K上拉电阻到VCC
    ******************************************************************************
**/
void DHT11_Output_Config(void)
{
    /* 使能GPIOG时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;

    /* 配置PG11为推挽输出，速度50MHz */
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;  // 推挽输出模式
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;       // 操作PG11引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // 高速模式确保时序准确

    GPIO_Init(GPIOG, &GPIO_InitStructure);

    /* 默认拉高总线（DHT11空闲状态要求） */
    GPIO_SetBits(GPIOG, GPIO_Pin_11);
}

/**
    ******************************************************************************
    * 函数名称：DHT11_Input_Config
    * 功能描述：配置PG11为浮空输入模式（用于接收传感器数据）
    * 输入参数：无
    * 返回值  ：无
    * 详细说明：
    *   1. 读取数据前必须切换为输入模式
    *   2. 依赖外部上拉电阻维持高电平
    * 硬件要求：
    *   - 必须连接4.7K上拉电阻到VCC
    ******************************************************************************
**/
void DHT11_Input_Config(void)
{
    /* 使能GPIOG时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;

    /* 配置PG11为浮空输入 */
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;  // 浮空输入模式
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;            // 操作PG11引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       // 速度参数对输入模式无影响

    GPIO_Init(GPIOG, &GPIO_InitStructure);
}

/**
    ******************************************************************************
    * 函数名称：DHT11_Config
    * 功能描述：DHT11初始化函数
    * 输入参数：无
    * 返回值  ：无
    * 详细说明：
    *   1. 默认配置为输出模式并拉高总线
    *   2. 实际使用时需要额外调用读取函数
    * 硬件要求：
    *   - 调用后需延迟至少1秒等待传感器稳定
    ******************************************************************************
**/
void DHT11_Config(void)
{
    DHT11_Output_Config();  // 初始化为输出模式
}

/**
    ******************************************************************************
    * 函数名称：DHT11_SendStart
    * 功能描述：发送起始信号（主机拉低总线18ms以上）
    * 输入参数：无
    * 返回值  ：无
    * 详细说明：
    *   1. 时序要求严格：
    *      - 拉低至少18ms（实际用20ms）
    *      - 拉高20-40us后切换输入模式（实际用30us）
    * 硬件要求：
    *   - 必须从输出模式调用
    ******************************************************************************
**/
void DHT11_SendStart(void)
{
    /* 确保为输出模式 */
    DHT11_Output_Config();

    /* 拉高总线（确保起始状态正确） */
    GPIO_SetBits(GPIOG, GPIO_Pin_11);

    /* 发送起始信号：拉低总线20ms */
    GPIO_ResetBits(GPIOG, GPIO_Pin_11);  // 拉低
    Delay_ms(20);                        // 保持低电平20ms（DHT11要求≥18ms）

    /* 释放总线（拉高）并等待30us */
    GPIO_SetBits(GPIOG, GPIO_Pin_11);    // 释放总线
    Delay_us(30);                        // 等待20-40us（DHT11准备响应）
}

/**
    ******************************************************************************
    * 函数名称：DHT11_IsACK
    * 功能描述：检测DHT11的应答信号
    * 输入参数：无
    * 返回值  ：true-应答成功，false-应答超时
    * 详细说明：
    *   1. 应答信号时序：
    *      - 传感器拉低80us
    *      - 传感器拉高80us
    *   2. 超时保护：单次等待不超过100us
    * 硬件要求：
    *   - 必须在发送起始信号后调用
    ******************************************************************************
**/
bool DHT11_IsACK(void)
{
    u8 cnt = 0;

    /* 切换为输入模式等待应答 */
    DHT11_Input_Config();

    /* 等待传感器拉低总线（超时100us） */
    while(GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_11) && cnt < 100){
        Delay_us(1);
        cnt++;
    }
    if(cnt >= 100){
		/* 等待传感器拉高总线（超时100us） */
    	cnt = 0;
		return false;
	}  // 超时未检测到拉低

    
    while(!GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_11) && cnt < 100){
        Delay_us(1);
        cnt++;
    }
    if(cnt >= 100){
		return false;
	}  // 超时未检测到拉高

    return true;  // 成功检测到应答信号
}

/**
    ******************************************************************************
    * 函数名称：DHT11_ReadBit
    * 功能描述：读取单比特数据
    * 输入参数：无
    * 返回值  ：0或1
    * 详细说明：
    *   1. 数据格式：
    *      - 0: 低电平26-28us
    *      - 1: 低电平26-28us后接高电平70us
    *   2. 采样点：低电平后等待40us检测电平状态
    * 硬件要求：
    *   - 必须在应答信号检测成功后调用
    ******************************************************************************
**/
u8 DHT11_ReadBit(void)
{
    /* 等待高电平结束（数据起始信号） */
    while(GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_11)){}  

    /* 等待低电平结束（数据位开始） */
    while(!GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_11)){} 

    /* 延迟40us后检测电平（区分0/1） */
    Delay_us(40);  
    return GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_11) ? 1 : 0;
}

/**
    ******************************************************************************
    * 函数名称：DHT11_ReadByte
    * 功能描述：读取单字节数据（8个连续位）
    * 输入参数：无
    * 返回值  ：8位数据
    * 详细说明：
    *   1. 数据格式：高位在前（MSB）
    *   2. 通过左移操作拼接各个比特
    * 硬件要求：
    *   - 依赖DHT11_ReadBit()实现
    ******************************************************************************
**/
u8 DHT11_ReadByte(void)
{
    u8 data = 0;
    for(int i = 0; i < 8; i++){
        data <<= 1;            // 左移腾出最低位
        data |= DHT11_ReadBit(); // 读取当前比特
    }
    return data;
}

/**
    ******************************************************************************
    * 函数名称：DHT11_ReadData
    * 功能描述：读取完整的40位数据（5字节）
    * 输入参数：DHT11_Buf[5] - 数据存储缓冲区
    * 返回值  ：true-读取成功，false-校验失败或通信错误
    * 详细说明：
    *   1. 数据格式：
    *      - [0]: 湿度整数
    *      - [1]: 湿度小数（DHT11固定为0）
    *      - [2]: 温度整数
    *      - [3]: 温度小数（DHT11固定为0）
    *      - [4]: 校验和（前4字节和）
    *   2. 两次读取间隔需≥1秒
    * 硬件要求：
    *   - 需保证传感器已正确上电
    ******************************************************************************
**/
bool DHT11_ReadData(u8 DHT11_Buf[5])
{
    /* 发送起始信号 */
    DHT11_SendStart();

    /* 检测应答信号 */
    if(true == DHT11_IsACK()){
        /* 连续读取5字节数据 */
        for(int i = 0; i < 5; i++){
            DHT11_Buf[i] = DHT11_ReadByte();
        }

        /* 校验数据（校验和=前4字节和） */
        if(DHT11_Buf[4] == DHT11_Buf[0] + DHT11_Buf[1] + DHT11_Buf[2] + DHT11_Buf[3]){
            return true;  // 校验成功
        }
        else{
            return false; // 校验失败
        }
    }
    else{
        return false;  // 应答失败
    }
}
